/*
 * Copyright (C) 2011-2013 Eric Lasota
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
#ifndef __RDX_ILCOMP_HPP__
#define __RDX_ILCOMP_HPP__

#include "rdx_basictypes.hpp"
#include "rdx_programmability.hpp"
#include "rdx_intrinsics.hpp"
#include "rdx_ilopcodes.hpp"

namespace RDX
{
	namespace Intrinsics
	{
		extern const char *OpcodeNames[];
	};

	namespace Programmability
	{
		struct Method;
	};
	namespace ObjectManagement
	{
		struct IObjectManager;
	};

	struct OperationContext;

	namespace ILCompiler
	{
		void CompileMethod(OperationContext *ctx, Programmability::Method *m, ObjectManagement::IObjectManager *objm);
		void JournalNativeMethodParameters(OperationContext *ctx, Programmability::Method *method, ObjectManagement::IObjectManager *objm);
		void CompactJournals(OperationContext *ctx, Programmability::Method *m, StackJournal *journals, LargeInt numJournals, ObjectManagement::IObjectManager *objm, LargeInt &outCompactSize, LargeInt &outNumDropped);

		union ILOpCompactArg
		{
			LargeInt li;
			LargeUInt lui;
			const void *p;
			UInt32 u32;
			UInt16 u16;
			UInt8 u8;

			Float f;
			Char c;
			Int si;
			Short s;
			Byte b;
			EnumValue ev;
			EnumValue::UnderlyingType evu;
			Bool bo;
		};

		
		union ILOpLargeArg
		{
			Double dbl;
			Long l;
			Programmability::RuntimePointer<void> rtp;
			UInt64 u64;
			ILOpCompactArg ca[2];
		};

		// The stack journal tracks the lifespan of objects on the stack at points where execution may stop.
		// Out-of-frame exceptions always cause a timeslice to stop with STATUS_EXCEPTION, except the root frame which
		// receives STATUS_FATAL instead.
		// Reverse-direction jumps will also decrement the timeslice counter, causing STATUS_SUSPENDED if too many occur

		//                                                May throw     Slice timer behavior      ILOP_tick inserted    Next instruction patched
		// - Calls to functions                           Yes           Decrement                 Yes                   Yes
		// - Jumps that go backwards                      No            Decrement if backwards    Yes                   No
		// - Throws                                       Yes           Suspend                   No                    Yes
		// - Array index lookups                          Yes           None                      No                    Yes
		// - Object NULL checks                           Yes           None                      No                    Yes
		// - Pointer mutability verification              Yes           None                      No                    Yes
		// - Polymorphic casts                            Yes           None                      Yes                   Yes
		struct StackJournal
		{
		private:
			inline static LargeInt PushPtr(UInt8 **bytesOutH, const void *p)
			{
				union { const void *p; UInt8 bytes[sizeof(void*)]; } u;
				u.p = p;
				if(*bytesOutH)
				{
					UInt8 *bytesOut = *bytesOutH;
					for(LargeInt i=0;i<sizeof(void*);i++)
						bytesOut[i] = u.bytes[i];
					(*bytesOutH) += sizeof(void*);
				}
				return sizeof(void*);
			}
			
			inline static void *PopPtr(const UInt8 **bytesInH, LargeInt &nBytes)
			{
				const UInt8 *bytesIn = *bytesInH;

				nBytes += sizeof(void*);
				union { void *p; UInt8 bytes[sizeof(void*)]; } u;

				u.p = NULL;	// Shut up compiler warning
				for(LargeInt i=0;i<sizeof(void*);i++)
					u.bytes[i] = bytesIn[i];
				(*bytesInH) += sizeof(void*);
				return u.p;
			}
			
			inline static LargeInt PushInt(UInt8 **bytesOutH, LargeInt v)
			{
				UInt8 bytes[sizeof(LargeInt)*2];
				LargeInt nBytes = 0;

				while(true)
				{
					Int8 extendedSmall = static_cast<Int8>((v & 0x7f) << 1);
					bytes[nBytes++] = static_cast<UInt8>(v & 0x7f);
					if(extendedSmall / 2 != v)
					{
						bytes[nBytes-1] |= 0x80;
						v >>= 7;
					}
					else
						break;
				}

				if(*bytesOutH)
				{
					UInt8 *outB = *bytesOutH;
					for(LargeInt i=0;i<nBytes;i++)
						(*outB++) = bytes[i];
					*bytesOutH = outB;
				}

				return nBytes;
			}
			
			inline static LargeInt PopInt(const UInt8 **bytesInH, LargeInt &nBytes)
			{
				LargeInt bitOffs = 0;
				LargeInt dec = 0;
				const UInt8 *bytesIn = *bytesInH;
				while(true)
				{
					nBytes++;
					UInt8 b = *bytesIn++;
					dec |= ((static_cast<LargeInt>(b) & 0x7f) << bitOffs);
					bitOffs += 7;

					if((b & 0x80) == 0)
						break;
				}
				LargeInt headRoom = static_cast<LargeInt>(sizeof(LargeInt) * 8) - bitOffs;
				// Sign extend
				if(headRoom > 0)
				{
					dec <<= headRoom;
					dec >>= headRoom;
				}
				*bytesInH = bytesIn;
				return dec;
			}

		public:
			const Programmability::Type *vType;
			LargeInt offset;
			LargeInt startInstruction;		// Instruction that creates this value.  The value starts existing on the NEXT instruction.
			LargeInt endInstruction;		// Instruction that destroys this value. The value stops existing on the NEXT instruction.
			bool isPointer;
			bool isVarying;
			bool isConstant;

			bool isNotNull;					// Can be guaranteed to be non-NULL after NullCheckOffset
			LargeInt notNullInstruction;	// IL instruction that enforces this value as non-NULL.  The value is guaranteed to be non-NULL on the NEXT instruction.

			bool isParameter;				// Offset is relative to PRV instead of BP, only trace if stack goes into native code
			bool isFromLocal;				// For integrity enforcement when serializing copy-to-pointer returns
			bool isTraceable;				// Contains traceable references
			const Programmability::String *name;				// For debugging
			LargeInt pointerSource;			// Offset in the same frame that this is from if it's a thread-relative pointer
			Programmability::PointerSourceType pointerSourceType;


			inline LargeInt Compress(UInt8 *bytesOut)
			{
				LargeInt nBytes = 0;
				nBytes += PushPtr(&bytesOut, vType);
				nBytes += PushPtr(&bytesOut, name);
				nBytes += PushInt(&bytesOut, offset);
				nBytes += PushInt(&bytesOut, startInstruction);
				nBytes += PushInt(&bytesOut, endInstruction);
				nBytes += PushInt(&bytesOut, notNullInstruction);
				nBytes += PushInt(&bytesOut, pointerSource);
				nBytes += PushInt(&bytesOut, pointerSourceType);
				LargeInt flags = 0;
				if(isPointer) flags |= 1;
				if(isVarying) flags |= 2;
				if(isConstant) flags |= 4;
				if(isNotNull) flags |= 8;
				if(isParameter) flags |= 16;
				if(isFromLocal) flags |= 32;
				if(isTraceable) flags |= 64;
				nBytes += PushInt(&bytesOut, flags);
				return nBytes;
			}

			inline LargeInt Decompress(const UInt8 *bytesIn)
			{
				LargeInt nBytes = 0;
				vType = static_cast<const Programmability::Type *>(PopPtr(&bytesIn, nBytes));
				name = static_cast<const Programmability::String *>(PopPtr(&bytesIn, nBytes));
				offset = PopInt(&bytesIn, nBytes);
				startInstruction = PopInt(&bytesIn, nBytes);
				endInstruction = PopInt(&bytesIn, nBytes);
				notNullInstruction = PopInt(&bytesIn, nBytes);
				pointerSource = PopInt(&bytesIn, nBytes);
				pointerSourceType = static_cast<Programmability::PointerSourceType>(PopInt(&bytesIn, nBytes));
				LargeInt flags = PopInt(&bytesIn, nBytes);

				isPointer = ((flags & 1) != 0);
				isVarying = ((flags & 2) != 0);
				isConstant = ((flags & 4) != 0);
				isNotNull = ((flags & 8) != 0);
				isParameter = ((flags & 16) != 0);
				isFromLocal = ((flags & 32) != 0);
				isTraceable = ((flags & 64) != 0);
				return nBytes;
			}
		};

		struct ExceptionHandlerJournal
		{
			LargeInt startInstruction;		// First instruction covered by this
			LargeInt endInstruction;		// Last instruction covered by this
			LargeInt handlerInstruction;	// Handler instruction to jump to
		};

		struct ILInstruction
		{
			RDX::ILOpcodes::ILOpcode opcode;
			union ArgPtrUnion
			{
				const ILOpCompactArg *ca;
				const ILOpLargeArg *la;
			} arg;
		};

		struct ILDebugInfo
		{
			LargeInt firstInstruction;
			const Programmability::String *filename;
			LargeInt line;
		};

		// Call points are reduced information about where call points are in a function, used to validate deserialized frames
		struct ILCallPoint
		{
			struct CallArgs
			{
				const Programmability::Method *func;
			};
			
			struct CallDelegateArgs
			{
				LargeInt offset;
				bool bpRelative;
			};

			struct CallVirtualArgs
			{
				LargeInt bpOffset;
				LargeInt vftOffset;
			};

			struct CallInterfaceArgs
			{
				LargeInt bpOffset;
				LargeInt vftOffset;
				const Programmability::StructuredType *soughtInterface;
			};

			ILOpcodes::ILOpcode ilOpcode;
			LargeInt instrNum;
			LargeInt paramBaseOffset;
			LargeInt prvOffset;

			union
			{
				CallArgs call;
				CallDelegateArgs callDelegate;
				CallVirtualArgs callVirtual;
				CallInterfaceArgs callInterface;
			} args;
		};
	};

	namespace SourceExport
	{
		void ExportSource(RDX::ObjectManagement::IObjectManager *objm, void *pf, ObjectManagement::Domain);
	}
};

#endif
